﻿/*
 * Original source by: Jeff Win's DotRas library
 * Copied from RasHelper.cs, NativeMethods.cs & UnsafeNativeMethods.cs
 */

using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Net;
using System;
using System.Security;

namespace TrafficSplitter
{
    class RasHelper
    {
        #region NativeMethods.cs
        /// <summary>
        /// The operation was successful.
        /// </summary>
        public const int SUCCESS = 0;

        /// <summary>
        /// The system cannot find the file specified.
        /// </summary>
        public const int ERROR_FILE_NOT_FOUND = 2;

        /// <summary>
        /// Defines the maximum length of a username.
        /// </summary>
        public const int UNLEN = 256;

        /// <summary>
        /// Defines the maximum length of a password.
        /// </summary>
        public const int PWLEN = 256;

        /// <summary>
        /// Defines the maximum length of a domain name.
        /// </summary>
        public const int DNLEN = 15;

        #region RASCREDENTIALS

        /// <summary>
        /// Describes user credentials associated with a phone book entry.
        /// </summary>
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 4)]
        public struct RASCREDENTIALS
        {
            public int size;
            public RASCM options;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = UNLEN + 1)]
            public string userName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = PWLEN + 1)]
            public string password;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = DNLEN + 1)]
            public string domain;
        }

        #endregion


        #region RASCM

        /// <summary>
        /// Defines the flags indicating which members of a <see cref="RASCREDENTIALS"/> instance are valid.
        /// </summary>
        [Flags]
        public enum RASCM
       {
            /// <summary>
            /// No options are valid.
            /// </summary>
            None = 0x0,

            /// <summary>
            /// The user name member is valid.
            /// </summary>
            UserName = 0x1,

            /// <summary>
            /// The password member is valid.
            /// </summary>
            Password = 0x2,

            /// <summary>
            /// The domain name member is valid.
            /// </summary>
            Domain = 0x4,
#if (WINXP || WINXPSP2 || WIN2K8 || WIN7)
            /// <summary>
            /// Indicates the credentials are the default credentials for an all-user connection.
            /// </summary>
            DefaultCredentials = 0x8,

            /// <summary>
            /// Indicates a pre-shared key should be retrieved.
            /// </summary>
            PreSharedKey = 0x10,

            /// <summary>
            /// Used to set the pre-shared key on the remote access server.
            /// </summary>
            ServerPreSharedKey = 0x20,

            /// <summary>
            /// Used to set the pre-shared key for a demand dial interface.
            /// </summary>
            DdmPreSharedKey = 0x40
#endif
        }

        #endregion
        #endregion

        #region UnsafeNativeMethods.cs

        /// <summary>
        /// Retrieves user credentials associated with a specified remote access phone book entry.
        /// </summary>
        /// <param name="lpszPhonebook">The full path and filename of a phone book file. If this parameter is a null reference, the default phone book is used.</param>
        /// <param name="lpszEntryName">The name of an existing entry within the phone book.</param>
        /// <param name="lpCredentials">Pointer to a <see cref="NativeMethods.RASCREDENTIALS"/> structure that upon return contains the requested credentials for the phone book entry.</param>
        /// <returns>If the function succeeds, the return value is zero.</returns>
        [DllImport("rasapi32.dll", CharSet = CharSet.Unicode)]
        private static extern int RasGetCredentials(
            string lpszPhonebook,
            string lpszEntryName,
            [In, Out] IntPtr lpCredentials);

        /// <summary>
        /// Retrieves user credentials associated with a specified remote access phone book entry.
        /// </summary>
        /// <param name="lpszPhonebook">The full path and filename of a phone book file. If this parameter is a null reference, the default phone book is used.</param>
        /// <param name="lpszEntryName">The name of an existing entry within the phone book.</param>
        /// <param name="lpCredentials">Pointer to a <see cref="NativeMethods.RASCREDENTIALS"/> structure that upon return contains the requested credentials for the phone book entry.</param>
        /// <returns>If the function succeeds, the return value is zero.</returns>
        public static int GetCredentials(string lpszPhonebook, string lpszEntryName, IntPtr lpCredentials)
        {
            return RasGetCredentials(lpszPhonebook, lpszEntryName, lpCredentials);
        }

        #endregion


        #region RasHelper.cs
        /// <summary>
        /// Retrieves user credentials associated with a specified remote access phone book entry.
        /// </summary>
        /// <param name="phoneBookPath">Required. The full path (including filename) of the phone book containing the entry.</param>
        /// <param name="entryName">Required. The name of the entry whose credentials to retrieve.</param>
        /// <param name="options">The options to request.</param>
        /// <returns>The credentials stored in the entry, otherwise a null reference (<b>Nothing</b> in Visual Basic) if the credentials did not exist.</returns>
        /// <exception cref="System.ArgumentException"><paramref name="phoneBookPath"/> or <paramref name="entryName"/> is an empty string or null reference (<b>Nothing</b> in Visual Basic).</exception>
        /// <exception cref="System.UnauthorizedAccessException">The caller does not have the required permission to perform the action requested.</exception>
        [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        public static NetworkCredential GetCredentials(string phoneBookPath, string entryName, RASCM options)
       {
            if (string.IsNullOrEmpty(phoneBookPath))
           {
               return null;
                //ThrowHelper.ThrowArgumentException("phoneBookPath", Resources.Argument_StringCannotBeNullOrEmpty);
            }

            if (string.IsNullOrEmpty(entryName))
           {
                //ThrowHelper.ThrowArgumentException("entryName", Resources.Argument_StringCannotBeNullOrEmpty);
               return null;
            }

            NetworkCredential retval = null;

            int size = Marshal.SizeOf(typeof(RASCREDENTIALS));

            RASCREDENTIALS credentials = new RASCREDENTIALS();

            credentials.size = size;
            credentials.options = options;

            IntPtr pCredentials = IntPtr.Zero;
            try
            {
                pCredentials = Marshal.AllocHGlobal(size);
                Marshal.StructureToPtr(credentials, pCredentials, true);

                try
                {
                    FileIOPermission permission = new FileIOPermission(FileIOPermissionAccess.Read, phoneBookPath);
                    permission.Demand();

                    int ret = GetCredentials(phoneBookPath, entryName, pCredentials);
                    if (ret == SUCCESS)
                   {
                        credentials = (RASCREDENTIALS)Marshal.PtrToStructure(pCredentials, typeof(RASCREDENTIALS));
                        if (credentials.options != RASCM.None)
                       {
                            retval = new NetworkCredential(
                                credentials.userName,
                                credentials.password,
                                credentials.domain);
                        }
                    }
                    else if (ret != ERROR_FILE_NOT_FOUND)
                   {
                        //ThrowHelper.ThrowRasException(ret);
                    }
                }
                catch (EntryPointNotFoundException)
               {
                    //ThrowHelper.ThrowNotSupportedException(Resources.Exception_NotSupportedOnPlatform);
                }
                catch (SecurityException)
               {
                    //ThrowHelper.ThrowUnauthorizedAccessException(Resources.Exception_AccessDeniedBySecurity);
                }
            }
            finally
           {
                if (pCredentials != IntPtr.Zero)
               {
                    Marshal.FreeHGlobal(pCredentials);
                }
            }

            return retval;
       }

        #endregion
    }
       
}
